#include <iostream>
#include <sys/select.h>
#include "Socket.hpp"
#include <memory>
class SelectServer
{
    const static int size = sizeof(fd_set) * 8;
    const static int defaultfd = -1;

public:
    SelectServer(uint16_t port) : _select(std::make_unique<TcpSocket>()), _isruning(false)
    {
        _select->BuildTcpSocketServer(port); // 构建服务器
        for(int i = 0;i<size;i++)
        _fds[i]=defaultfd;
        // 先将监听套接字放入辅助数组
        _fds[0] = _select->Fd();
    }
    void Start()
    {
        _isruning = true;
        while (_isruning)
        {
            // 监听时有客户端访问就相当于读端准备好了
            fd_set fdr;
            // 初始化+将需要监视的套接字设置进去
            FD_ZERO(&fdr);
            // 将还需要继续关心的文件重新添加到fdr对应位置中
            int maxfd = -1;
            for (int i = 0; i < size; i++)
            {
                if (_fds[i] == defaultfd)
                    continue;
                FD_SET(_fds[i], &fdr);
                maxfd = _fds[i]>maxfd?_fds[i]:maxfd;
            }
            for(int i = 0;i<size;i++)
            {
                if(_fds[i]==defaultfd)
                continue;
                std::cout<<_fds[i];
            }
            std::cout<<std::endl;
            // struct timeval time = {1, 0};
            int n = select(maxfd + 1, &fdr, nullptr, nullptr, nullptr); // 检测我们所关心的文件描述符是否就绪
            //LOG(LogLevel::DEBUG)<<"select ing...";
            // 就绪n>0,等待时间超时n=0,检测失败n<0
            switch (n)
            {
            case 0:
                LOG(LogLevel::DEBUG) << "timewait out...";
                break;
            case -1:
                LOG(LogLevel::FATAL) << "select false";
                break;
            default:
                // 分配，准备好的fd可能是listenfd，也可能是普通fd
                Assignment(fdr);
                break;
            }
        }
    }
    void Assignment(fd_set &fdr)
    {
        // 先遍历整个辅助数组依次对就绪的fd进行处理
        for (int i = 0; i < size; i++)
        {
            if (_fds[i] == defaultfd)
                continue;
            if (FD_ISSET(_fds[i], &fdr)) // 如果该文件读就绪
            {
                // 检测是listenfd还是普通读写fd
                if (_fds[i] == _select->Fd())
                {
                    // 是监听套接字
                    // 建立连接
                    Accept();
                }
                else
                {
                    // 读写
                    Recv(_fds[i], i);
                }
            }
        }
    }
    void Accept()
    {
        InetAddr client;
        int sockfd = _select->Accept(&client);
        if (sockfd < 0)
        {
            LOG(LogLevel::ERROR) << "建立连接失败";
            return;
        }
        else
        {
            LOG(LogLevel::DEBUG)<<"accept success"<<sockfd;
            // 建立连接成功，此时读缓冲区可能还没有数据就绪，需要将该文件描述符添加到辅助数组
            int pos = 0;
            for (; pos < size; pos++)
            {
                if (_fds[pos] == defaultfd)
                    break;
            }
            // 此时该位置必定为-1
            if (pos == size)
            {
                LOG(LogLevel::ERROR) << "_fds[] full";
                close(sockfd);
            }
            else
            {
                // 直接将该文件描述符添加到辅助数组，进行select
                _fds[pos] = sockfd;
            }
        }
    }
    void Recv(int fd, int pos)
    {
        char buffer[1024];
        ssize_t size = recv(fd, buffer, sizeof(buffer) - 1, 0);
        if (size > 0)
        {
            buffer[size] = 0;
            std::cout << "client say@ " << buffer << std::endl;
        }
        else if (size == 0)
        {
            LOG(LogLevel::DEBUG) << "client quit";
            close(_fds[pos]);
            _fds[pos] = -1;
        }
        else
        {
            LOG(LogLevel::FATAL) << "recv 异常";
            close(_fds[pos]);
            _fds[pos] = -1;
        }
    }
    ~SelectServer()
    {
    }

private:
    std::unique_ptr<Socket> _select;
    bool _isruning;
    int _fds[size];
};